package com.zhb.cloud.plant.active.service.impl;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.zhb.cloud.base.BaseService;
import com.zhb.cloud.component.RedisUtil;
import com.zhb.cloud.core.util.PrizeUtils;
import com.zhb.cloud.plant.active.mapper.PrizeMapper;
import com.zhb.cloud.plant.active.mapper.UserMapper;
import com.zhb.cloud.plant.active.mapper.UserPrizeMapper;
import com.zhb.cloud.plant.active.model.Prize;
import com.zhb.cloud.plant.active.model.User;
import com.zhb.cloud.plant.active.model.UserPrize;
import com.zhb.cloud.plant.active.service.UserService;
import com.zhb.cloud.utils.UUIDUtil;

@Service
@Transactional
public class UserServiceImpl extends BaseService implements UserService {
	Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
	@Autowired
	private UserMapper userMapper;
	@Autowired
	private RedisUtil redisUtil;
	@Autowired
	private PrizeMapper prizeMapper;
	@Autowired
	private UserPrizeMapper userPrizeMapper;

	/**
	 * 抽奖的具体实现
	 * 
	 * @throws TimeoutException
	 */
	@Override
	public Prize lottery(String userId) {
		logger.info(userId + "哈哈我开始抽奖了");
		// 1.查询奖品
		List<Prize> prizes = prizeMapper.findAllPrize();
		// 1.1计算获得奖品
		Prize prize = PrizeUtils.getAwardPrize(prizes);
		logger.info("中的奖品是" + prize);
		String prizeName = prize.getPrizeName();// 奖品名称
		boolean lock = false; // 锁的默认状态
		// 抽奖操作
		try {
			while (!lock) { // 防止虚假唤醒
				// 获取锁的最大超时时间
				Long maxSleepMills = System.currentTimeMillis() + RedisUtil.MAX_SLEEP_TIME;
				// 锁的最大占用时间
				Long keepTimes = System.currentTimeMillis() + RedisUtil.KEEP_TIME;
				// 尝试获得锁
				lock = redisUtil.setNX(prizeName, keepTimes);
				logger.info("尝试获得锁,锁的状态: " + lock + "当前锁定的奖品是: " + prizeName);
				if (lock) {
					logger.info("获得锁,锁的状态: " + lock + "当前锁定的奖品是: " + prizeName);
					// 如果获得锁
					redisUtil.expire(prizeName, keepTimes);// 设置过期时间
					// 业务处理
					logger.info("业务处理,当前操作的奖品是: " + prizeName);
					awardOperation(userId, prize.getId(), prizeName);
				} else if (!lock) {// 没有获得锁
					/*
					 * 官方解释: 1.C4发送SETNX lock.foo来获取锁
					 * 2.崩溃的客户端C3仍然持有它，所以Redis将回复0到C4。 3.C4发送GET
					 * lock.foo来检查锁是否过期。如果不是，它会睡眠一段时间，从一开始就重试。
					 * 4.相反，如果由于lock.foo上的Unix时间比当前的Unix时间早， 锁定过期， C4会尝试执行：
					 * GETSET lock.foo <当前Unix时间戳+锁定超时+ 1> 5.由于GETSET语义，
					 * C4可以检查存储在密钥中的旧值是否仍然是过期的时间戳。如果是这样的话，就得到了锁。
					 * 6.如果另一个客户端（例如C5）比C4速度更快，并通过GETSET操作获取锁定， 则C4
					 * GETSET操作将返回一个未过期的时间戳。
					 * C4将从第一步重启。请注意，即使C4在未来几秒钟内设置密钥，这也不是问题。
					 */
					// 获得时间
					Long lockTime = (Long) redisUtil.get(prizeName);
					// 获得旧时间
					Long oldLockTime = (Long) redisUtil.getAndSet(prizeName,
							System.currentTimeMillis() + RedisUtil.KEEP_TIME);
					if (lockTime < System.currentTimeMillis() && oldLockTime < System.currentTimeMillis()) { // 锁定过期
						logger.info("获得锁,锁的状态: " + lock + "当前锁定的奖品是: " + prizeName);
						lock = true; // 锁定
						// 业务处理
						logger.info("业务处理,当前操作的奖品是: " + prizeName);
						awardOperation(userId, prize.getId(), prize.getPrizeName());
					}
				} else { // 仍然未获得锁阻塞
					// 继续等待获取锁
					// 如果超过最大等待时间抛出异常
					// log.info("稍后重新请求锁...");
					if (System.currentTimeMillis() > maxSleepMills) {
						throw new TimeoutException("请求等待超时");
					}
					TimeUnit.MILLISECONDS.sleep(RedisUtil.MAX_SLEEP_TIME); // 休眠6秒
				}
			}
		} catch (Exception e) {
			prize = null; // 有异常的情况
			e.printStackTrace();
		} finally {
			// 为了使这个锁定算法更健壮，持有一个锁的客户端应该总是检查超时没有过期，
			// 然后用DEL解锁密钥，因为客户端的失败可能很复杂，不仅会崩溃，
			// 而且会阻止大量的操作并尝试在很长一段时间后发出DEL（当LOCK已经被另一个客户端占用时）。
			// 删除锁
			redisUtil.delete(prizeName);
			logger.info("删除锁");
		}
		return prize;// 返回获得是奖品
	}

	/**
	 * @Description:抽奖处理
	 * @author: zhb zhb1316783262@163.com
	 * @date: 2017年11月20日 上午11:14:50
	 * @param userId
	 * @param prizeId
	 * @param prizeName
	 */
	public void awardOperation(String userId, String prizeId, String prizeName) {
		logger.info("进入方法awardOperation 业务处理开始,当前操作的奖品是: " + prizeName + "抽奖人" + userId);
		reducePrizeNumber(prizeId); // 更新奖品数量
		reduceAwardCount(userId);// 更新用户抽奖次数
		recordUserAward(userId, prizeId);// 插入抽奖记录
	}

	/**
	 * @Description: 减少奖品数量
	 * @author: zhb zhb1316783262@163.com
	 * @date: 2017年11月20日 上午11:00:08
	 * @param prizeName
	 * @param userId
	 */
	public void reducePrizeNumber(String prizeId) {
		logger.info("进入方法reducePrizeNumber减少奖品的数量");
		Prize prize = prizeMapper.selectByPrimaryKey(prizeId);
		prize.setPrizeAmount(prize.getPrizeAmount() - 1);
		prizeMapper.updateByPrimaryKeySelective(prize);
	}

	/**
	 * 更新用户剩余抽奖次数
	 */
	@Override
	public int reduceAwardCount(String userId) {
		logger.info("进入方法reduceAwardCount 减少用户的抽奖次数");
		User user = userMapper.selectByPrimaryKey(userId);
		user.setAwardCount(user.getAwardCount() - 1);
		return userMapper.updateByPrimaryKeySelective(user);
	}

	/**
	 * 插入用户的抽奖记录
	 */
	@Override
	public int recordUserAward(String userId, String prizeId) {
		logger.info("进入方法recordUserAward 记录抽奖记录");
		UserPrize userPrize = new UserPrize();
		userPrize.setId(UUIDUtil.getId());
		userPrize.setPrizeId(prizeId);
		userPrize.setUserId(userId);
		userPrize.setStatus(1);
		userPrize.setCreateTime(new Date());
		return userPrizeMapper.insertSelective(userPrize);
	}

	@Override
	public User findById(String id) {
		return null;
	}

	@Override
	public int saveModel(User model) {
		return 0;
	}

	@Override
	public int updateModel(User model) {
		return 0;
	}

	@Override
	public int deleteModel(String id) {
		return 0;
	}

	@Override
	public long totalAmount(User model, Object... objects) {
		return 0;
	}

	/**
	 * 批量插入
	 */
	@Override
	public void batchInsert(List<User> list) {
		userMapper.batchInsert(list);
	}

	@Override
	public List<User> selectModelByCondition(Object... objects) {
		return userMapper.findAllUsers();

	}
}
